/*
 * Copyright 2011 Fabian Wenzelmann
 * 
 * This file is part of Recognition-Procedures-for-Boolean-Functions.
 * 
 * Recognition-Procedures-for-Boolean-Functions is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Recognition-Procedures-for-Boolean-Functions is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Recognition-Procedures-for-Boolean-Functions.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.wenzelmf.booleanFunction.lpb.combinatorial.solver;

import java.util.HashMap;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import de.wenzelmf.booleanFunction.dnf.DNF;
import de.wenzelmf.booleanFunction.lpb.combinatorial.splittingTree.SplittingTree;
import de.wenzelmf.booleanFunction.lpb.combinatorial.splittingTree.SplittingTreeNode;
import de.wenzelmf.booleanFunction.lpb.combinatorial.splittingTree.TreeContext;
import de.wenzelmf.booleanFunction.parser.XMLDocumentException;
import de.wenzelmf.booleanFunction.util.IntegerValue;
import de.wenzelmf.booleanFunction.util.Tuple;

/**
 * Implementation of the Interface {@link SolverInformation} that doubles the the complete system
 * (intervals and coefficients).
 * 
 * @author Fabian Wenzelmann
 * @version 1.0
 *
 */
public class DoubleSolverInformation implements SolverInformation
{

	/**
	 * Stores the factor each coefficient must be multiplied with to get the correct value.
	 */
	protected Integer[] factors;
	/**
	 * The coefficients (to get the correct coefficient a value in this array must be multiplied)
	 * with the corresponding factor.
	 */
	protected Integer[] coefficients;
	/**
	 * A interval in column <i>k - 1</i> must be multiplied with {@code intervalFactors[k]} to return
	 * the correct interval.
	 */
	protected Integer[] intervalFactors;
	/**
	 * Stores the intervals of the tree (must be multiplied with the corresponding factor).
	 */
	protected HashMap<Integer, HashMap<Integer, Tuple<IntegerValue, IntegerValue>>> intervals;
	/**
	 * The tree context of the tree this solver information was created for.
	 */
	protected TreeContext context;
	
	/**
	 * The value that is used as value of the {@link SolverInformation#TYPE_NAME} attribute.
	 */
	public static final String TYPE = "doubleSolverInformation";
	
	@Override
	public void init(int columns, DNF dnf, TreeContext context, SplittingTree t)
	{
		this.factors = new Integer[columns];
		this.coefficients = new Integer[columns];
		this.intervalFactors = new Integer[columns];
		this.context = context;
		// TODO: size?
		this.intervals = new HashMap<Integer, HashMap<Integer, Tuple<IntegerValue, IntegerValue>>>(columns * 2);
	}

	@Override
	public Integer getCoefficient(int column)
	{
		if(coefficients[column] == null)
			return null;
		return getFactor(column) * coefficients[column];
	}

	@Override
	public Integer setCoefficient(int column, int value)
	{
		Integer old = getCoefficient(column);
		this.factors[column] = 1;
		this.coefficients[column] = value;
		return old;
	}

	@Override
	public Tuple<IntegerValue, IntegerValue> getInterval(SplittingTreeNode n)
	{
		int column = n.getColumn();
		int row = n.getRow();
		
		HashMap<Integer, Tuple<IntegerValue, IntegerValue>> col = getColumn(column);
		int factor = getIntervalFactor(column);
		if(factor == 1)
			return col.get(row);
		else
		{
			Tuple<IntegerValue, IntegerValue> t = col.get(row);
			return new Tuple<IntegerValue, IntegerValue>(t.getFirst().multiply(factor), t.getSecond().multiply(factor));
		}
	}

	@Override
	public Tuple<IntegerValue, IntegerValue> setInterval(SplittingTreeNode n,
			Tuple<IntegerValue, IntegerValue> value)
	{
		int column = n.getColumn();
		
		HashMap<Integer, Tuple<IntegerValue, IntegerValue>> col = getColumn(column);
		return col.put(n.getRow(), value);
	}
	
	/**
	 * Return the hash map that represents the specified column.
	 * 
	 * Creates a new hash map if non entry for this column existed yet.
	 * 
	 * @param column 0-based column number in the table.
	 * 
	 * @return The hash map that stores the intervals of the specified column.
	 */
	private HashMap<Integer, Tuple<IntegerValue, IntegerValue>> getColumn(int column)
	{
		HashMap<Integer, Tuple<IntegerValue, IntegerValue>> col = intervals.get(column);
		if(col == null)
		{
			// TODO: again not nice
			col = new HashMap<Integer, Tuple<IntegerValue, IntegerValue>>(context.getColumnSize(column) * 2);
			// TODO: size
			intervals.put(column, col);
		}
		return col;
	}

	@Override
	public Tuple<IntegerValue, IntegerValue> solveConflict(int column, DNF dnf, IntegerValue max,
			IntegerValue min, TreeContext context, SplittingTree t) {
		intervalFactors[column] = getIntervalFactor(column) * 2;
		
		for(int i = column + 1; i < coefficients.length; i++)
		{
			factors[i] = getFactor(i) * 2;
			intervalFactors[i] = getIntervalFactor(i) * 2;
		}
		
		return new Tuple<IntegerValue, IntegerValue>(max.multiply(2), min.multiply(2));
	}
	
	/**
	 * Returns the factor of the specified column (0-based).
	 * 
	 * @param column The column number in the table, 0-based.
	 * 
	 * @return The factor in the specified column.
	 */
	private int getFactor(int column)
	{
		if(factors[column] == null)
			factors[column] = 1;
		return factors[column];
	}
	
	/**
	 * Returns the interval factor of the specified column (0-based).
	 * 
	 * @param column The column number in the table, 0-based.
	 * 
	 * @return The interval factor in the specified column.
	 */
	private int getIntervalFactor(int column)
	{
		if(intervalFactors[column] == null)
			intervalFactors[column] = 1;
		return intervalFactors[column];
	}
	
	// TODO: test
	@Override
	public Document toXMLDocument() throws XMLDocumentException
	{
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		Document result = null;
		try
		{
			result = documentBuilderFactory.newDocumentBuilder().newDocument();
		}catch (ParserConfigurationException e)
		{
			throw new XMLDocumentException(e);
		} catch(FactoryConfigurationError e)
		{
			throw new XMLDocumentException(e);
		}
		Element root = result.createElement(TAG_NAME);
		root.setAttribute(TYPE_NAME, TYPE);
		root.setAttribute("columns", "" + intervals.size());
		result.appendChild(root);
		
		int col = 0;
		for(int i = 0; i < intervals.size(); i++)
		{
			HashMap<Integer, Tuple<IntegerValue, IntegerValue>> column = intervals.get(new Integer(col));
			Element columnEntry = result.createElement("column");
			columnEntry.setAttribute("columnID", "" + col);
			columnEntry.setAttribute("nodes", "" + context.getColumnSize(col));
			
			for(Tuple<IntegerValue, IntegerValue> interval : column.values())
			{
				Element nodeEntry = result.createElement("node");
				nodeEntry.setAttribute("minimumDegree", "" + interval.getFirst());
				nodeEntry.setAttribute("maximumDegree", "" + interval.getSecond());
				
				columnEntry.appendChild(nodeEntry);
			}
			col++;
			root.appendChild(columnEntry);
		}
		result.setXmlStandalone(true);
		return result;
	}

}
